/* Copyright 2024 Coremail
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import type { BusinessError } from '@ohos.base';
import socket from '@ohos.net.socket';
import wifiManager from '@ohos.wifiManager';
import connection from '@ohos.net.connection';
import util from '@ohos.util';

const tag = 'zhtest ';

function int2ip(n: number): string {
  return `${(n & 0xFF000000) >>> 24}.${(n & 0x00FF0000) >>> 16}.${(n & 0x0000FF00) >>> 8}.${(n & 0xFF)}`;
}

function getLocalIp(): string {
  const ipInfo = wifiManager.getIpInfo();
  console.info(tag + 'local ipInfo : ' + JSON.stringify(ipInfo));
  const localIp = int2ip(ipInfo.ipAddress);
  return localIp;
}

class SocketInfo {
  message: ArrayBuffer = new ArrayBuffer(1);
  remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}

function joinBuffers(buffers: Uint8Array[]): Uint8Array {
  const size = buffers.reduce((acc, cur) => acc + cur.byteLength, 0);
  const r = new Uint8Array(size);
  buffers.reduce((acc, cur) => {
    r.set(cur, acc);
    return acc + cur.byteLength;
  }, 0);
  return r;
}

function delay(timeout: number): Promise<void> {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(), timeout);
  });
}

export enum NetworkState {
  CONNECTING = 0,
  CONNECTED = 1,
  DISCONNECTED = 2,
  ERROR = 3,
}

export class TcpSocketTest {
  serverHost: string = 'imap.qq.com';
  serverPort: number = 143;
  serverHost2: string = '172.16.10.110';
  serverPort2: number = 25;
  sockets: socket.TCPSocket[];
  state = NetworkState.DISCONNECTED;
  p: Promise<void> = Promise.resolve();

  constructor() {

  }

  async sendManyData(serverHost: string, serverPort: number) {
    try {
      this._sendManyData(serverHost, serverPort);
    } catch (e) {
      console.log(tag, JSON.stringify(e));
    }
  }
  async _sendManyData(serverHost: string, serverPort: number) {
    console.log(tag, "connect", serverHost, serverPort)
    const localIp = getLocalIp();
    let netStatus = connection.hasDefaultNetSync();
    const handler = await connection.getDefaultNet();
    const netAddressArray: connection.NetAddress[] = await handler.getAddressesByName(serverHost);
    let serverIp: string = '';
    for (let i = 0; i < netAddressArray.length; i++) {
      if (this.isIP(netAddressArray[i].address)) {
        serverIp = netAddressArray[i].address;
        break;
      }
    }
    const tcpSocket: socket.TCPSocket = socket.constructTCPSocketInstance();
    // 本地IP和Port
    let localNetAddress: socket.NetAddress = {
      address: localIp,
      port: 0,
      family: 1
    };
    // 绑定本地IP和Port
    await tcpSocket.bind(localNetAddress);
    let buffers: Uint8Array[] = [];
    tcpSocket.on("message", (message: SocketInfo) => {
      console.log(tag + 'tcpSocket, on(message) : ' + JSON.stringify(message));
      let uint8Array = new Uint8Array(message.message);
      let textDecoder = util.TextDecoder.create();
      let msgFromServer = textDecoder.decodeWithStream(uint8Array);
      buffers.push(uint8Array);
    });
    tcpSocket.on("close", () => {
      console.info(tag + 'tcpSocket, on(close)');
      this.state = NetworkState.DISCONNECTED;
    });
    tcpSocket.on("connect", () => {
      console.info(tag + 'tcpSocket, on(connect)');
      this.state = NetworkState.CONNECTED;
    });
    tcpSocket.on("error", (error: Error) => {
      console.info(tag + 'tcpSocket, on(error) : ' + JSON.stringify(error));
      this.state = NetworkState.DISCONNECTED;
    });
    let tcpConnectOptions = {
      address: {
        address: serverIp,
        port: serverPort,
      },
      timeout: 6000
    };
    await tcpSocket.connect(tcpConnectOptions);
    let needCheckResponse = true;
    for (let i = 0; i < 5 && buffers.length == 0 && needCheckResponse; i++) {
      console.log(tag, 'no data');
      await delay(500);
    }
    if (buffers.length == 0) {
      console.log(tag, 'no data received')
      return;
    }
    async function sendAndReceive(data: string) {
      console.log(tag, 'send', data.substring(0, 20).replace('\r\n', '\\r\\n'));
      await tcpSocket.send({ data });
      for (let i = 0; i < 50 && buffers.length == 0 && needCheckResponse; i++) {
        console.log(tag, 'no data');
        await delay(500);
      }
      if (buffers.length == 0) {
        console.log(tag, 'no data received')
        return;
      }
      let textDecoder = util.TextDecoder.create();
      let u8a = joinBuffers(buffers)
      let msg = textDecoder.decodeWithStream(u8a);
      buffers = [];
      console.info(tag, 'receive', msg);
      return msg;
    }
    let textEncoder = new util.TextEncoder();
    const email = "fff@607.net";
    const password = "123"
    const base64 = new util.Base64Helper();

    this.p = new Promise(async (resolve, reject) => {
      await sendAndReceive(`EHLO ${serverHost}\r\n`);
      await sendAndReceive("AUTH LOGIN\r\n");
      const user = await base64.encodeToString(textEncoder.encodeInto(email))
      await sendAndReceive(`${user}\r\n`);
      const pass = await base64.encodeToString(textEncoder.encodeInto(password))
      await sendAndReceive(`${pass}\r\n`);
      resolve();
    })
    this.sockets.push(tcpSocket);
    return;
    let msg = await sendAndReceive(`MAIL FROM: <${email}>\r\n`);
    if (msg && msg.startsWith('503')) {
      await sendAndReceive(`EHLO ${serverHost}\r\n`);
      await sendAndReceive(`MAIL FROM: <${email}>\r\n`);
    }
    await sendAndReceive(`RCPT TO: <abc@cc.com>\r\n`);
    await sendAndReceive(`DATA\r\n`);

    needCheckResponse = false;
    await sendAndReceive(`FROM: ${email}\r\n`);
    await sendAndReceive(`TO: abc@cc.com\r\n`);
    await sendAndReceive(`subject: hello\r\n\r\n`);
    const size = 100 * 1024;
    let arr = new Array(size);
    for (let i = 0; i < size; i++) {
      arr[i] = 'a';
    }
    const d = arr.join('');
    for (let i = 0; i < 300; i++) {
      await sendAndReceive("data" + i.toString() + ":" + d);
    }
    needCheckResponse = true;
    await sendAndReceive(`\r\n.\r\n`);
    await sendAndReceive(`QUIT\r\n`)
    tcpSocket.close();
  }

  async connectTest(serverHost: string, serverPort: number): Promise<void> {
    try {
      console.debug(tag + 'connectTest, serverHost = ' + serverHost + ', serverHost = ' + serverPort);
      // 获取本地IP
      const localIp = getLocalIp();
      console.info(tag + 'localIp : ' + localIp, serverPort);
      // 网络是否正常
      let netStatus = connection.hasDefaultNetSync();
      console.info(tag + 'netStatus : ' + JSON.stringify(netStatus));
      // 服务器的域名解析
      const handler = await connection.getDefaultNet();
      const netAddressArray: connection.NetAddress[] = await handler.getAddressesByName(serverHost);
      console.info(tag + 'server netAddressArray : ' + JSON.stringify(netAddressArray));
      // 寻找有效的服务器IP
      let serverIp: string = '';
      for (let i = 0; i < netAddressArray.length; i++) {
        if (this.isIP(netAddressArray[i].address)) {
          serverIp = netAddressArray[i].address;
          break;
        }
      }
      console.info(tag + 'serverIp : ' + serverIp);
      if (!serverIp) {
        console.info(tag + 'serverIp is err, return');
        return;
      }
      // 创建tcpsocket
      const tcpSocket: socket.TCPSocket = socket.constructTCPSocketInstance();
      // 本地IP和Port
      let localNetAddress: socket.NetAddress = {
        address: localIp,
        port: 0,
        family: 1
      };
      // 绑定本地IP和Port
      tcpSocket.bind(localNetAddress)
        .then(() => {
          console.info(tag + 'tcpSocket bind success');
          // 注册监听事件
          tcpSocket.on("connect", () => {
            console.info(tag + 'tcpSocket, on(connect)');
          });
          tcpSocket.on("message", (message: SocketInfo) => {
            console.log(tag + 'tcpSocket, on(message) : ' + JSON.stringify(message));
            let uint8Array = new Uint8Array(message.message);
            let textDecoder = util.TextDecoder.create();
            let msgFromServer = textDecoder.decodeWithStream(uint8Array);
            console.log(tag + 'tcpSocket, on(message) , msgFromServer : ' + JSON.stringify(msgFromServer));
          });
          tcpSocket.on("close", () => {
            console.info(tag + 'tcpSocket, on(close)');
          });
          tcpSocket.on("error", (error: Error) => {
            console.info(tag + 'tcpSocket, on(error) : ' + JSON.stringify(error));
          });
          console.info(tag + 'tcpSocket start to connect');
          // 连接参数
          let tcpConnectOptions = {
            address: {
              address: serverIp,
              port: serverPort,
            },
            timeout: 6000
          };
          // 连接服务器
          tcpSocket.connect(tcpConnectOptions)
            .then(() => {
              console.info(tag + 'tcpSocket connect success');
              // 待发送的数据
              let data = 'hello, ' + serverHost;
              let tcpSendOptions: socket.TCPSendOptions = {
                data: data
              };
              // 向服务器发送数据
              tcpSocket.send(tcpSendOptions)
                .then(() => {
                  console.info(tag + 'tcpSocket send success');
                  // 关闭连接
                  // tcpSocket.close()
                  //   .then(() => {
                  //     console.info(tag + 'tcpSocket close success');
                  //   })
                  //   .catch((err: BusinessError) => {
                  //     console.info(tag + 'tcpSocket close err : ' + JSON.stringify(err));
                  //   });
                })
                .catch((err: BusinessError) => {
                  console.info(tag + 'tcpSocket send err : ' + JSON.stringify(err));
                })
              this.sockets.push(tcpSocket);
            })
            .catch((err: BusinessError) => {
              console.info(tag + 'tcpSocket connect err : ' + JSON.stringify(err));
            })
        })
        .catch((err: BusinessError) => {
          console.info(tag + 'tcpSocket bind err : ' + JSON.stringify(err));
        })
    } catch (err) {
      console.info(tag + 'connectTest err : ' + JSON.stringify(err));
    }
  }

  isIP(str: string): boolean {
    if (str.length < 7 || str.length > 15) {
      return false;
    }
    let arr: string[] = str.split(".");
    if (arr.length != 4) {
      return false;
    }
    for (let i = 0; i < 4; i++) {
      let s = arr[i];
      for (let j = 0; j < s.length; j++) {
        if (s.charAt(j) < "0" || s.charAt(j) > "9") {
          return false;
        }
      }
    }
    for (let i = 0; i < 4; i++) {
      let temp = parseInt(arr[i]);
      if (temp < 0 || temp > 255) {
        return false;
      }
    }
    return true;
  }
}